/*!	 surface.cpp
**	 Template File
**
**	Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
**	Copyright (c) 2012-2013 Carlos López
**
**	This package is free software; you can redistribute it and/or
**	modify it under the terms of the GNU General Public License as
**	published by the Free Software Foundation; either version 2 of
**	the License, or (at your option) any later version.
**
**	This package is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
**	General Public License for more details.
**
** === N O T E S ===========================================================
**
** ========================================================================= */

#ifdef USING_PCH
#	include "pch.h"
#else
#ifdef HAVE_CONFIG_H
#	include <config.h>
#endif

#include "canvas.h"
#include "surface.h"
#include "target_scanline.h"
#include "target_cairo.h"
#include "target_tile.h"
#include "general.h"
#include <synfig/localization.h>

#ifdef HAS_VIMAGE
#include <Accelerate/Accelerate.h>
#endif

#endif

using namespace synfig;
using namespace std;
using namespace etl;

class target2surface : public synfig::Target_Tile
{
public:
    String engine;
    Surface *surface;
    bool	 sized;
public:
    target2surface(Surface *surface, String engine):
        surface(surface), sized()
    {
        set_engine(engine);
    }

    virtual bool start_frame(synfig::ProgressCallback * /* cb */)
    {
        set_tile_w(desc.get_w());
        set_tile_h(desc.get_h());
        return true;
    }

    bool add_tile(const synfig::Surface &surface, int /* x */, int /* y */)
    {
        *this->surface = surface;
        return true;
    }

    void end_frame() { }
};

class target2surface_scanline : public synfig::Target_Scanline
{
public:
    Surface *surface;
    bool	 sized;

    target2surface_scanline(Surface *surface):
        surface(surface), sized() { }

    virtual bool set_rend_desc(synfig::RendDesc *newdesc)
    {
        assert(newdesc);
        assert(surface);
        return synfig::Target_Scanline::set_rend_desc(newdesc);
    }

    virtual bool start_frame(synfig::ProgressCallback * /* cb */)
    {
        if (surface->get_w() != desc.get_w() || surface->get_h() != desc.get_h()) {
            surface->set_wh(desc.get_w(), desc.get_h());
        }

        return true;
    }

    virtual void end_frame()
    { }

    virtual Color * start_scanline(int scanline)
    {
        return (*surface)[scanline];
    }

    virtual bool end_scanline()
    {
        return true;
    }
};

class target2cairo_image: public synfig::Target_Cairo
{
public:
    cairo_surface_t** image;

    target2cairo_image(cairo_surface_t ** s):
        image(s) { }

    virtual bool set_rend_desc(synfig::RendDesc *newdesc)
    {
        assert(newdesc);
        return synfig::Target_Cairo::set_rend_desc(newdesc);
    }

    virtual bool obtain_surface(cairo_surface_t*& s)
    {
        int sw = cairo_image_surface_get_width(*image);
        int sh = cairo_image_surface_get_height(*image);
        int w = desc.get_w(), h = desc.get_h();

        if (sw != w || sh != h) {
            cairo_surface_destroy(*image);
            *image = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h);
        }

        s = cairo_surface_reference(*image);
        return true;
    }
};

Target_Tile::Handle
synfig::surface_target(Surface *surface, const String &engine)
{
    return Target_Tile::Handle(new target2surface(surface, engine));
}

Target_Scanline::Handle
synfig::surface_target_scanline(Surface *surface)
{
    return Target_Scanline::Handle(new target2surface_scanline(surface));
}

Target_Cairo::Handle
synfig::cairo_image_target(cairo_surface_t **surface)
{
    return Target_Cairo::Handle(new target2cairo_image(surface));
}

void
synfig::Surface::clear()
{
#ifdef HAS_VIMAGE
    fill(Color(0.5, 0.5, 0.5, 0.0000001));
#else
    etl::surface<Color, ColorAccumulator, ColorPrep>::clear();
#endif
}

void
synfig::Surface::blit_to(alpha_pen& pen, int x, int y, int w, int h)
{
    static const float epsilon(0.00001);
    const float alpha(pen.get_alpha());

    if (pen.get_blend_method() == Color::BLEND_STRAIGHT && fabs(alpha - 1.0f) < epsilon) {
        if (x >= get_w() || y >= get_w()) {
            return;
        }

        // clip source origin
        if (x < 0) {
            w += x;	// decrease
            x = 0;
        }

        if (y < 0) {
            h += y;	// decrease
            y = 0;
        }

        // clip width against dest width
        w = min((long)w, (long)(pen.end_x() - pen.x()));
        h = min((long)h, (long)(pen.end_y() - pen.y()));

        // clip width against src width
        w = min(w, get_w() - x);
        h = min(h, get_h() - y);

        if (w <= 0 || h <= 0) {
            return;
        }

        for (int i = 0; i < h; i++) {
            char* src(static_cast<char*>(static_cast<void*>(operator[](y) + x)) + i * get_w()*sizeof(Color));
            char* dest(static_cast<char*>(static_cast<void*>(pen.x())) + i * pen.get_width()*sizeof(Color));
            memcpy(dest, src, w * sizeof(Color));
        }

        return;
    }

#ifdef HAS_VIMAGE

    if (pen.get_blend_method() == Color::BLEND_COMPOSITE && fabs(alpha - 1.0f) < epsilon) {
        if (x >= get_w() || y >= get_w()) {
            return;
        }

        // clip source origin
        if (x < 0) {
            // u-=x;	// increase
            w += x;	// decrease
            x = 0;
        }

        if (y < 0) {
            // v-=y;	// increase
            h += y;	// decrease
            y = 0;
        }

        // clip width against dest width
        w = min(w, pen.end_x() - pen.x());
        h = min(h, pen.end_y() - pen.y());

        // clip width against src width
        w = min(w, get_w() - x);
        h = min(h, get_h() - y);

        if (w <= 0 || h <= 0) {
            return;
        }

        vImage_Buffer top, bottom;
        vImage_Buffer& dest(bottom);

        top.data = static_cast<void*>(operator[](y) + x);
        top.height = h;
        top.width = w;
        // top.rowBytes=get_w()*sizeof(Color); // TODO:  this should get the pitch!!
        top.rowBytes = get_pitch();

        bottom.data = static_cast<void*>(pen.x());
        bottom.height = h;
        bottom.width = w;
        // bottom.rowBytes=pen.get_width()*sizeof(Color); // TODO:  this should get the pitch!!
        bottom.rowBytes = pen.get_pitch(); // TODO:  this should get the pitch!!

        vImage_Error ret;
        ret = vImageAlphaBlend_ARGBFFFF(&top, &bottom, &dest, kvImageNoFlags);

        assert(ret != kvImageNoError);

        return;
    }

#endif
    etl::surface<Color, ColorAccumulator, ColorPrep>::blit_to(pen, x, y, w, h);
}

void
synfig::CairoSurface::blit_to(alpha_pen& pen, int x, int y, int w, int h)
{
    static const float epsilon(0.00001);
    const float alpha(pen.get_alpha());

    if (pen.get_blend_method() == Color::BLEND_STRAIGHT && fabs(alpha - 1.0f) < epsilon) {
        if (x >= get_w() || y >= get_w()) {
            return;
        }

        // clip source origin
        if (x < 0) {
            w += x;	// decrease
            x = 0;
        }

        if (y < 0) {
            h += y;	// decrease
            y = 0;
        }

        // clip width against dest width
        w = min((long)w, (long)(pen.end_x() - pen.x()));
        h = min((long)h, (long)(pen.end_y() - pen.y()));

        // clip width against src width
        w = min(w, get_w() - x);
        h = min(h, get_h() - y);

        if (w <= 0 || h <= 0) {
            return;
        }

        for (int i = 0; i < h; i++) {
            char* src(static_cast<char*>(static_cast<void*>(operator[](y) + x)) + i * get_w()*sizeof(CairoColor));
            char* dest(static_cast<char*>(static_cast<void*>(pen.x())) + i * pen.get_width()*sizeof(CairoColor));
            memcpy(dest, src, w * sizeof(CairoColor));
        }

        return;
    } else {
        etl::surface<CairoColor, CairoColorAccumulator, CairoColorPrep>::blit_to(pen, x, y, w, h);
    }
}

void
CairoSurface::set_wh(int w, int h, int /*pitch*/)
{
    // If cs_ has been set then don't resize it.
    // Only the target which created the specific cairo surface can do that.
    if (cs_ != NULL) {
        synfig::warning("Cannot resize a Cairo Surface directly. Use its Target_Cairo instance");
        return;
    }

    // decrease reference counter in case we have a valid cs_image_
    if (cs_image_ != NULL) {
        cairo_surface_destroy(cs_image_);
    }

    // create a new cairo_surface image type
    cs_image_ = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h);

    // check wheter the new cairo surface is sane
    if (!cairo_surface_status(cs_image_)) {
        int stride(cairo_image_surface_get_stride(cs_image_));
        unsigned char* data(cairo_image_surface_get_data(cs_image_));
        // pass the infromation to the etl::surface to be used directly.
        set_wh(w, h, data, stride);
    } else {
        synfig::warning("CairoSurface::set_wh failed");
    }
}

void
CairoSurface::set_cairo_surface(cairo_surface_t *cs)
{
    if (cs == NULL) {
        if (is_mapped()) {
            unmap_cairo_image();
        }

        cairo_surface_destroy(cs_);
        cs_ = NULL;
        return;
    }

    if (cairo_surface_status(cs)) {
        synfig::error("CairoSurface received a non valid cairo_surface_t");

        if (is_mapped()) {
            unmap_cairo_image();
        }

        cairo_surface_destroy(cs_);
        cs_ = NULL;
        return;
    } else {
        if (is_mapped()) {
            unmap_cairo_image();
        }

        cairo_surface_destroy(cs_);
        cs_ = cairo_surface_reference(cs);
    }
}

cairo_surface_t*
CairoSurface::get_cairo_surface()const
{
    if (cs_ == NULL) {
        return NULL;
    } else {
        return cairo_surface_reference(cs_);
    }
}

cairo_surface_t*
CairoSurface::get_cairo_image_surface()const
{
    if (cs_image_ == NULL) {
        return NULL;
    } else {
        return cairo_surface_reference(cs_image_);
    }
}

bool
CairoSurface::map_cairo_image()
{
    if (!cs_ || cs_image_) {
        synfig::error("Attempting to map a NULL surface or a surface already mapped)");
        return false;
    }

    if (cairo_surface_get_type(cs_) != CAIRO_SURFACE_TYPE_IMAGE) {
#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 12, 0)
        cs_image_ = cairo_surface_map_to_image(cs_, NULL);
#else
        assert(false); // Old versions of cairo are not supported
#endif
    } else {
        cs_image_ = cairo_surface_reference(cs_);
    }

    if (cs_image_ != NULL) {
        cairo_surface_flush(cs_image_);
        cairo_format_t t = cairo_image_surface_get_format(cs_image_);

        if (t == CAIRO_FORMAT_ARGB32 || t == CAIRO_FORMAT_RGB24) {
            unsigned char* data(cairo_image_surface_get_data(cs_image_));
            int w(cairo_image_surface_get_width(cs_image_));
            int h(cairo_image_surface_get_height(cs_image_));
            int stride(cairo_image_surface_get_stride(cs_image_));
            set_wh(w, h, data, stride);
            return true;
        }

        return false;
    }

    return false;
}

void
CairoSurface::unmap_cairo_image()
{
    if (!cs_ || !cs_image_) {
        synfig::error("Attempting to unmap a NULL surface or a surface not mapped)");
        return;
    }

    cairo_surface_mark_dirty(cs_image_);

    if (cairo_surface_get_type(cs_) != CAIRO_SURFACE_TYPE_IMAGE) {
#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 12, 0)
        // this will destroy cs_image_
        cairo_surface_unmap_image(cs_, cs_image_);
        cs_image_ = NULL;
#else
        assert(false); // Old versions of cairo are not supported
#endif
    } else {
        cairo_surface_destroy(cs_image_);
        cs_image_ = NULL;
    }
}

bool
CairoSurface::is_mapped()const
{
    return (cs_image_ != NULL);
}